#include "oh/RenderNodeImplTS.h"
#include "oh/OHEnv.h"
#include "src/core/SkTraceEvent.h"

void RenderNodeImplTS::Destructor(napi_env env, void *nativeObject, [[maybe_unused]] void *finalize_hint) {
    delete reinterpret_cast<OHRenderNode *>(nativeObject);
}

napi_value RenderNodeImplTS::CreateJsNode() {
    napi_env env = OHEnv::GetInstance()->GetEnv();
    if (env == nullptr || OHRenderNode::gNodeConstructorRef == nullptr) {
        return nullptr;
    }
    napi_value constructor;
    napi_status status = napi_get_reference_value(env, OHRenderNode::gNodeConstructorRef, &constructor);
    if (status != napi_ok) {
        return nullptr;
    }
    
    napi_value instance = nullptr;
    status = napi_new_instance(env, constructor, 0, nullptr, &instance);
    if (status != napi_ok) {
        return nullptr;
    }
    return instance;
}

RenderNodeImplTS::~RenderNodeImplTS() {
    napi_value napi_ptr;
    auto env = OHEnv::GetInstance()->GetEnv();
    napi_handle_scope scope;
    napi_open_handle_scope(env, &scope);

    // @todo 可能node已经销毁了，但是node的js对象还在访问使用
    napi_create_external(env, nullptr, nullptr, nullptr, &napi_ptr);
    napi_set_named_property(env, getLocalJsObject(), "OHRenderNodePtr", napi_ptr);

    if (fHasRef) {
        napi_delete_reference(env, fJsObject);
        fHasRef = false;
    }
    napi_close_handle_scope(env, scope);
}

void *RenderNodeImplTS::getNodeObject() {
    return getLocalJsObject();
}

void RenderNodeImplTS::initNode(void *UINode, OHRenderNode *selfNode) {
    if (UINode == nullptr) {
        UINode = CreateJsNode();
    }
    napi_value jsNode = (napi_value)UINode;
    napi_env env = OHEnv::GetInstance()->GetEnv();
    if (!jsNode || !env) {
        return;
    }
    
    napi_value napi_ptr, pivot;
    napi_create_external(env, selfNode, nullptr, nullptr, &napi_ptr);
    napi_status status = napi_set_named_property(env, jsNode, "OHRenderNodePtr", napi_ptr);
    status = napi_create_reference(env, jsNode, 1, &fJsObject);

    napi_value napi_zero;
    napi_get_named_property(env, jsNode, "pivot", &pivot);
    napi_create_double(env, 0.0, &napi_zero);
    status = napi_set_named_property(env, pivot, "x", napi_zero);
    status = napi_set_named_property(env, pivot, "y", napi_zero);
    status = napi_set_named_property(env, jsNode, "pivot", pivot);
    
    char id_str[100];
    snprintf(id_str, 99, "%d", selfNode->fNodeId);
    id_str[99] = 0;
    napi_value strValue;
    napi_create_string_utf8(env, id_str, strlen(id_str), &strValue);
    status = napi_set_named_property(env, jsNode, "label", strValue);

    // use for debug
//    napi_value napi_false;
//    napi_get_boolean(env, false, &napi_false);
//    napi_set_named_property(env, instance, "clipToFrame", napi_false);

//    status = napi_wrap(env, instance, this, OHRenderNode::NoneDestructor, nullptr, &fJsObject);
    if (status != napi_ok) {
      return;
    }
    fHasRef = true;
}


void RenderNodeImplTS::appendRootNode(std::shared_ptr<OHRenderNode> root) {
    if (!root) {
        return;
    }
    if (root->getNodeType() == RenderNodeType::NODE_UNINIT) {
        root->setNodeImpl(std::make_unique<RenderNodeImplTS>());
        root->initNode(nullptr);
    }
    napi_value node = (napi_value)root->getNodeObject();
    if (!node) {
        return;
    }
    
    // if OHRenderNode has fCallback, it is not control by shared_ptr, so we cannot get weak_ptr
    invokeJsFunction("appendChild", 1, &node, nullptr, nullptr);
//    NodeStatusModify::GetInstance()->pushTo(NodeStatusModify::APPEND_CHILD, fJsObject,
//                                            root_node->fJsObject, nullptr);
}

napi_value RenderNodeImplTS::setLiveWithJs(OHRenderNode *selfNode) {
    auto env = OHEnv::GetInstance()->GetEnv();
    
    auto value = getLocalJsObject();
    napi_status status = napi_add_finalizer(env, value, selfNode, RenderNodeImplTS::Destructor, nullptr, nullptr);
    if (status != napi_ok) {
        return nullptr;
    }
    
    if (fHasRef) {
        uint32_t refCount = 0;
        napi_reference_unref(env, fJsObject, &refCount);
    }
    return value;
}

napi_status RenderNodeImplTS::invokeJsFunction(const char *name, size_t argc, const napi_value* argv, napi_value *result, napi_value node) {
    TRACE_EVENT0("skia", TRACE_STR_COPY(name));

    auto env = OHEnv::GetInstance()->GetEnv();
    napi_value localObject = node;
    if (localObject == nullptr) {
        localObject = getLocalJsObject();
    }
    
    napi_value fun;
    napi_status status = napi_get_named_property(env, localObject, name, &fun);
    if (status != napi_ok) {
        return status;
    }
    
    status = napi_call_function(env, localObject, fun, argc, argv, result);
    if (status != napi_ok) {
        SkDebugf("error invoke status %d!", status);
        return status;
    }
    return napi_ok;
}

napi_value RenderNodeImplTS::getLocalJsObject() {
    auto env = OHEnv::GetInstance()->GetEnv();

    napi_value localObject;
    napi_status status = napi_get_reference_value(env, fJsObject, &localObject);
    if (status != napi_ok) {
        return nullptr;
    }
    return localObject;
}

void RenderNodeImplTS::prepareRedraw(OHRenderNode *node) {
    auto env = OHEnv::GetInstance()->GetEnv();
    uint result = 0;
    napi_reference_ref(env, fJsObject, &result);
}

void RenderNodeImplTS::doRedraw(OHRenderNode *node) {
    auto env = OHEnv::GetInstance()->GetEnv();
    NodeStatusModify::GetInstance()->setModifyNodeType(RenderNodeType::NODE_ARKTS);
    napi_handle_scope scope;
    napi_open_handle_scope(env, &scope);

    {
        // Used to trigger RequestNextVSync and must not be removed.
        napi_value localObject;
        napi_status status = napi_get_reference_value(env, fJsObject, &localObject);
        if (status == napi_ok) {
            napi_value napi_true;
            napi_get_boolean(env, true, &napi_true);
            napi_set_named_property(env, localObject, "clipToFrame", napi_true);
        }
    }
    
    node->doRedraw();
    uint result = 0;
    napi_reference_unref(env, fJsObject, &result);
    napi_close_handle_scope(env, scope);
}


SkRect RenderNodeImplTS::getNodeSize() {
    auto env = OHEnv::GetInstance()->GetEnv();
    napi_value frame;
    napi_value localObject;
    napi_status status = napi_get_reference_value(env, fJsObject, &localObject);
    if (status != napi_ok) {
        return {};
    }
    napi_get_named_property(env, localObject, "frame", &frame);

    napi_value napi_x, napi_y, napi_width, napi_height;
    napi_get_named_property(env, frame, "x", &napi_x);
    napi_get_named_property(env, frame, "y", &napi_y);
    napi_get_named_property(env, frame, "width", &napi_width);
    napi_get_named_property(env, frame, "height", &napi_height);
    double x = 0, y = 0, w = 0, h = 0;
    napi_get_value_double(env, napi_x, &x);
    napi_get_value_double(env, napi_y, &y);
    napi_get_value_double(env, napi_width, &w);
    napi_get_value_double(env, napi_height, &h);
    return SkRect::MakeXYWH(x * OHRenderNode::gVP2PXRatio, y * OHRenderNode::gVP2PXRatio, w * OHRenderNode::gVP2PXRatio,
                            h * OHRenderNode::gVP2PXRatio);
}

void RenderNodeImplTS::nodeDraw(OH_Drawing_Canvas *oh_canvas, OHRenderNode *selfNode) {
    if (selfNode->fPictureCmd) {
        OH_Drawing_CanvasTranslate(oh_canvas, selfNode->fOffsetX, selfNode->fOffsetY);
        OHDrawingAPI::OH_Drawing_CanvasDrawRecordCmdNesting(oh_canvas, selfNode->fPictureCmd.get());
        selfNode->fNodeNeedRedraw = false;
    }
}

void RenderNodeImplTS::FillOpsForBatch(NodeStatusModify::NodeBatchContext &ctx, NodeStatusModify::NodeModifyInfo &info,
                                       void *const (&op_nodes)[3]) {
    using Op = NodeStatusModify::NodeOp;

    ctx.ops.push_back(info.fOp);
    ctx.nodes.push_back(op_nodes[0]);
    switch (info.fOp) {
    case Op::INVALIDATE:
        break;
    case Op::REMOVE_CHILD:
        ctx.nodes.push_back(op_nodes[1]);
        break;
    case Op::INSERT_CHILD_AFTER:
        ctx.nodes.push_back(op_nodes[1]);
        ctx.nodes.push_back(op_nodes[2]);
        break;
    case Op::CLEAR_CHILDREN:
        break;
    case Op::SIZE:
    case Op::POSITION:
        info.fNumbers[0] /= OHRenderNode::gVP2PXRatio;
        info.fNumbers[1] /= OHRenderNode::gVP2PXRatio;
        ctx.numbers.insert(ctx.numbers.end(), info.fNumbers, info.fNumbers + 2);
        break;
    case Op::TRANSFORM:
        ctx.numbers.insert(ctx.numbers.end(), info.fNumbers, info.fNumbers + 16);
        break;
    case Op::SHAPE_CLIP:
        ctx.numbers.insert(ctx.numbers.end(), info.fNumbers, info.fNumbers + 4);
        break;
    case Op::SHAPE_CLIP_RRECT:
        ctx.numbers.insert(ctx.numbers.end(), info.fNumbers, info.fNumbers + 12);
        break;
    case Op::SHAPE_CLIP_PATH:
        ctx.strs.push_back(info.fStr);
        break;
    case Op::APPEND_CHILD:
        ctx.nodes.push_back(op_nodes[1]);
        break;
    case Op::MARK_GROUP:
        break;
    case Op::MARK_NO_GROUP:
        break;
    default:
        break;
    }
}

void RenderNodeImplTS::ApplyBatchModify(NodeStatusModify::NodeBatchContext &ctx, napi_value localObject) {
    auto env = OHEnv::GetInstance()->GetEnv();
    size_t length = ctx.ops.size() * sizeof(int) + ctx.numbers.size() * sizeof(float);
    napi_value arrayBuffer;
    void *data = nullptr;
    napi_create_arraybuffer(env, length, &data, &arrayBuffer);
    memcpy(data, ctx.ops.data(), ctx.ops.size() * sizeof(int));
    memcpy((char *)data + ctx.ops.size() * sizeof(int), ctx.numbers.data(), ctx.numbers.size() * sizeof(float));

    napi_value int32Array;
    napi_create_typedarray(env, napi_int32_array, ctx.ops.size(), arrayBuffer, 0, &int32Array);

    napi_value float32Array;
    napi_create_typedarray(env, napi_float32_array, ctx.numbers.size(), arrayBuffer, ctx.ops.size() * sizeof(int),
                           &float32Array);

    napi_value nodeArray;
    napi_create_array_with_length(env, ctx.nodes.size(), &nodeArray);
    for (int i = 0; i < ctx.nodes.size(); i++) {
        napi_set_element(env, nodeArray, i, (napi_value)ctx.nodes[i]);
    }

    napi_value svgStrArray;
    napi_create_array_with_length(env, ctx.strs.size(), &svgStrArray);
    for (size_t i = 0; i < ctx.strs.size(); ++i) {
        napi_value strValue;
        napi_create_string_utf8(env, ctx.strs[i].c_str(), ctx.strs[i].size(), &strValue);
        napi_set_element(env, svgStrArray, i, strValue);
    }

    napi_set_named_property(env, localObject, "opArray", int32Array);
    napi_set_named_property(env, localObject, "numberArray", float32Array);
    napi_set_named_property(env, localObject, "nodeArray", nodeArray);
    napi_set_named_property(env, localObject, "svgStrArray", svgStrArray);

    napi_value fun;
    napi_status status = napi_get_named_property(env, localObject, "doModify", &fun);
    if (status != napi_ok) {
        SkDebugf("cannot get js doModify function! status %d", status);
        return;
    }

    TRACE_EVENT0("skia", "callJS");
    status = napi_call_function(env, localObject, fun, 0, nullptr, nullptr);
    if (status != napi_ok) {
        SkDebugf("error invoke doModify ret status %d!", status);
        return;
    }
}
